# -*- coding: utf-8 -*-

# $Id: base.py 2 2012-09-01 15:54:17Z alexis.roda.villalonga@gmail.com $

"""This module defines some base skeletons.
"""

import django

from dja_skel.lib.model_introspection import ModelIntrospector
from dja_skel.lib.python_package import DjangoApp
from dja_skel.lib.python_package import DjangoProject
from dja_skel.lib.skeleton import Skeleton
from dja_skel.lib import utils


class BaseDjangoSkel(Skeleton):
    """Base class for skeletons related to django.

    Context:

    * django_version: a tuple that stores the django version. For
      example ``(1, 4, 1)``

    """
    sumary = "Not documented."
    template_engine = "cheetah"

    def get_extra_context(self, context):
        super(BaseDjangoSkel, self).get_extra_context(context)
        context["django_version"] = django.VERSION
        return context


class BaseProjectSkel(BaseDjangoSkel):
    """Base class for skeletons that operate on a django project.

    Attributes:

    * project: a :class:`dja_skel.lib.python_package.DjangoProject`
      instance.
    """

    def __init__(self):
        super(BaseProjectSkel, self).__init__()
        self.project = None

    def before_instantiation(self, *args, **kw):
        super(BaseProjectSkel, self).before_instantiation(*args, **kw)
        self.project = DjangoProject()


class BaseAppSkel(BaseProjectSkel):
    """Base class for skeletons that operate on an existing django
    app.

    Attributes:

    * applabel: the application's label

    * app: a :class:`dja_skel.lib.python_package.DjangoApp`
      instance.
    """

    def __init__(self):
        super(BaseAppSkel, self).__init__()
        self.applabel = None
        self.app = None

    def before_instantiation(self, *args, **kw):
        super(BaseAppSkel, self).before_instantiation(*args, **kw)
        self.applabel = self._context["applabel"]
        self.app = DjangoApp(self.applabel)


class BaseModelSkel(BaseAppSkel):
    """Base class for skeleton that operate on a django model.

    Attributes:

    * model_name: name of the model

    * model: model class, it may be ``None`` if the model does not
      exits (we are creating it).

    Context:

    * model_name: uncamelized and underscorized version of the model
      name. For example ``my_model``.

    * Model_name: capitalized and whitespaced version of the model
      name. For example ``My model``.

    * modelname: lowercased version of the model name. For example
      ``mymodel``.

    * model: a ``dja_skel.lib.model_introspection.ModelIntrospector``
      instance.
    """

    def __init__(self, ):
        super(BaseModelSkel, self).__init__()
        self.model_name = None
        self.model = None

    def before_instantiation(self, *args, **kw):
        super(BaseModelSkel, self).before_instantiation(*args, **kw)
        self.model_name = self._context["ModelName"]
        self.model = getattr(self.app.models, self.model_name, None)

    def get_extra_context(self, context):
        super(BaseModelSkel, self).get_extra_context(context)
        context["model_name"] = utils.uncamelize(self.model_name)
        context["Model_name"] = utils.uncamelize(context["ModelName"],
                                                 sep=" "
                                                 ).capitalize()
        context["modelname"] = context["ModelName"].lower()
        context["model"] = ModelIntrospector(self.model)
        return context


class ViewSkelMixin(object):
    """This mixin provides common funcionality useful for skeletons that
    generate views.

    Usually classes using this mixin will override the
    :meth:`get_view_module_map` and :meth:`get_view_url_map` methods
    and call the :meth:`add_view_imports` and :meth:`add_view_urls`
    methods from the :meth:`post_instantiation` method.

    Markers:

    * views/__init__.py: ``-*- VIEW IMPORTS -*-``

    * urls.py: ``-*- VIEW URLS -*-``
    """

    def get_view_module_map(self):
        """Returns a list of pairs ``(module, view)``, where ``view``
        is the name of a view created by the skeleton and ``module``
        is the name of the module where the view has been
        created. Subclasses must override this method.

        If this method returns ``[("mymodule", "myview")]`` the mixin
        will insert ``from <applabel>.views.mymodule import myview``
        into the ``views/__init__.py`` module before the ``-*- VIEW
        IMPORTS -*-`` marker.
        """
        return []

    def get_view_url_map(self):
        """Returns a list of pairs ``(url, view)``, where ``view`` is
        the name of a view created by the skeleton and ``url`` is the
        URL pattern for that view.

        I this method returns ``[("myurlpattern", "myview")]`` the
        mixin will insert ``url(r"myurlpattern", views.myview,
        name='myview'),`` into the ``urls.py`` module.
        """
        return []

    def add_view_imports(self):
        """This method gathers information about the created views (by
        calling :meth:`get_view_module_map`) and inserts the
        appropriate imports into the ``views/__init__.py`` module.
        """
        if self.app.is_module("views"):
            # views.py defines the views, so no imports are required
            return
        # views is a package, so imports are required in
        # views/__init__.py
        init = self.app.get_file(
            "views/__init__.py",
            encoding=self._encoding
            )
        lines = []
        for module, view in self.get_view_module_map():
            lines.append(
                u"from {applabel}.views.{module} import {view}\n".format(
                    applabel=self._context["applabel"],
                    module=module,
                    view=view
                    )
                )
        init.insert_before(lines, "VIEW IMPORTS")
        init.save()

    def add_view_urls(self):
        """This method gathers information about the created views (by
        calling :meth:`get_view_url_map`) and inserts the appropriate
        url patterns into the ``urls.py`` module.
        """
        urls = self.app.get_file(
            "urls.py",
            encoding=self._encoding
            )
        lines = []
        for pattern, view_name in self.get_view_url_map():
            lines.append(u'url(r"{pattern}",\n'.format(pattern=pattern))
            lines.append(u'    views.{view_name},\n'.format(view_name=view_name))
            lines.append(u'    name="{view_name}"\n'.format(view_name=view_name))
            lines.append(u'    ),\n')
        urls.insert_before(lines, "VIEW URLS")
        urls.save()


class BaseViewSkel(BaseAppSkel, ViewSkelMixin):
    """Base class for skeletons that generate a view.

    It inherits from :class:`BaseAppSkel` and :class:`ViewSkelMixin`
    and overrides the :meth:`after_instantiation` method in order to
    call the :meth:`ViewSkelMixin.add_view_imports` and
    :meth:`ViewSkelMixin.add_view_urls` methods.
    """
    def after_instantiation(self):
        super(BaseViewSkel, self).after_instantiation()
        # add imports to views/__init__.py
        self.add_view_imports()
        # add urls to <app>/urls.py
        self.add_view_urls()


class BaseModelViewSkel(BaseModelSkel, ViewSkelMixin):
    """Base class for skeletons that generate a view somewhat related
    to a model.

    It inherits from :class:`BaseModelSkel` and :class:`ViewSkelMixin`
    and overrides the :meth:`after_instantiation` method in order to
    call the :meth:`ViewSkelMixin.add_view_imports` and
    :meth:`ViewSkelMixin.add_view_urls` methods.
    """
    def after_instantiation(self):
        super(BaseModelViewSkel, self).after_instantiation()
        # add imports to views/__init__.py
        self.add_view_imports()
        # add urls to <app>/urls.py
        self.add_view_urls()

